package core

import (
	grt "runtime"
	"time"
	"kumachan/standalone/ctn"
)


type EventLoop struct {
	h_timer  *timerHeap
	q_micro  chan <- func()
	q_MACRO  chan <- func()
}
func CreateEventLoop() *EventLoop {
	var h_timer = createTimerHeap()
	var q_micro = make(chan func(), 4096)
	var q_MACRO = make(chan func(), 4096)
	go (func() {
		grt.LockOSThread()
		for {
			select {
			case k := <- q_micro: k()
			default:
				select {
				case k := <- q_MACRO: k()
				case k := <- q_micro: k()
				case <- h_timer.notify:
					if k, ok := h_timer.shift(); ok { k() }
				}
			}
		}
	})()
	return &EventLoop {
		h_timer: h_timer,
		q_micro: q_micro,
		q_MACRO: q_MACRO,
	}
}
func (e *EventLoop) addTimer(ms int, n int, ctx *context, k func()) {
	var dur = (time.Millisecond * time.Duration(ms))
	e.h_timer.insert(dur, n, ctx, k)
}
// note: DO NOT call synchronously: can cause deadlock
func (e *EventLoop) asyncAddTask_micro(k func()) {
	e.q_micro <- k
}
// note: DO NOT call synchronously: can cause deadlock
func (e *EventLoop) asyncAddTask_MACRO(k func()) {
	e.q_MACRO <- k
}

type timerHeap struct {
	data    ctn.MutHeap[*timer]
	notify  <- chan time.Time
}
type timer struct {
	baseTime  time.Time
	duration  time.Duration
	repeat    int
	context   *context
	callback  func()
}
func timerDue(a *timer) (bool,time.Duration) {
	var delta = time.Now().Sub(a.baseTime)
	return (delta >= a.duration), (a.duration - delta)
}
func timerLessThan(a *timer, b *timer) bool {
	return (a.baseTime.Sub(b.baseTime) < (b.duration - a.duration))
}
func createTimerHeap() *timerHeap {
	return &timerHeap {
		data:   ctn.MakeMutHeap(timerLessThan),
		notify: nil,
	}
}
func (h *timerHeap) shift() (func(), bool) {
	var first, ok = h.data.First()
	if !(ok) {
		return nil, false
	}
	{ var due, dur = timerDue(first)
	if !(due) {
		h.notify = time.After(dur)
		return nil, false
	}}
	var this = first
	h.data.Shift()
	defer (func() {
		if first, ok := h.data.First(); ok {
			var due, dur = timerDue(first)
			if due {
				h.notify = time.After(0)
			} else {
				h.notify = time.After(dur)
			}
		}
	})()
	if this.context.isDisposed() {
		return nil, false
	}
	if this.repeat != 0 {
		var new_repeat_count int
		if this.repeat < 0 {
			// infinite
			new_repeat_count = this.repeat
		} else {
			new_repeat_count = (this.repeat - 1)
		}
		var repeat = &timer {
			baseTime: time.Now(),
			duration: this.duration,
			repeat:   new_repeat_count,
			context:  this.context,
			callback: this.callback,
		}
		h.data.Insert(repeat)
	}
	return this.callback, true
}
func (h *timerHeap) insert(dur time.Duration, n int, ctx *context, k func()) {
	if n == 0 {
		return
	}
	if dur < 0 {
		dur = 0
	}
	var a = &timer {
		baseTime: time.Now(),
		duration: dur,
		repeat:   (n - 1),
		context:  ctx,
		callback: k,
	}
	var first, has_first_before = h.data.First()
	h.data.Insert(a)
	if !(has_first_before) || (has_first_before && timerLessThan(a, first)) {
		h.notify = time.After(dur)
	}
}


